package com.dhz.common.id.redis;

import com.dhz.common.redis.RedisServerConf;
import com.dhz.common.redis.RedisTemplate;
import com.google.common.base.Optional;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.exceptions.JedisConnectionException;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
 * 生成连续的ID接口
 *
 * @author FY
 */
public class IdGeneratorTemplate extends RedisTemplate {
    private static final Logger logger = LoggerFactory.getLogger(IdGeneratorTemplate.class);
    /** 尝试次数 */
    private static final int DEFAULT_MAX_ATTEMPTS = 5;

    private final int maximumAttempts;

    public IdGeneratorTemplate() {
        this(DEFAULT_MAX_ATTEMPTS);
    }

    public IdGeneratorTemplate(final int maximumAttempts) {
        this.maximumAttempts = maximumAttempts;
    }

    /**
     * 生成一个ID
     * @param tag ID所属model，如user
     * @return 该ID被封装在Guava的{@link Optional}中
     */
    public IdObject next(final String tag) {
        Optional<List<IdObject>> optIds = generateIds(tag, 1);

        if (optIds.isPresent()) {
            return optIds.get().get(0);
        }

        throw new IdGeneratorException("生成ID失败.");
    }

    /**
     * 批量生产ID，将生成默认数量为{@link IdObject#MAX_BATCH_SIZE}数量的ID
     * @param tag ID所属model，如user
     * @return ids list
     */
    public List<IdObject> batchId(final String tag) {
        return batchId(tag, IdObject.MAX_BATCH_SIZE);
    }

    /**
     * 批量生产ID
     * @param tag ID所属model，如user
     * @param size 传入要生成的ID数量，上限是{@link IdObject#MAX_BATCH_SIZE}
     * @return 返回ID集合
     */
    public List<IdObject> batchId(final String tag, final long size) {
        Optional<List<IdObject>> optIds = generateIds(tag, size);

        if (optIds.isPresent()) {
            return optIds.get();
        }

        return Collections.emptyList();
    }

    /**
     * 批量生产ID
     * @param size 传入要生成的ID数量，上显示{@link IdObject#MAX_BATCH_SIZE}
     * @return 返回ID集合
     */
    private Optional<List<IdObject>> generateIds(final String tag, final long size) {
        // size检查
        if (size <= 0 || size > IdObject.MAX_BATCH_SIZE) {
            throw new IdGeneratorException(String.format("批量生产ID的size超出限定范围 1 ~  %d", IdObject.MAX_BATCH_SIZE));
        }

        // 生成ID，失败重试 maximumAttempts次
        for (int i=0; i<maximumAttempts; i++) {
            try {
                Optional<List<IdObject>> optIds = generateIds(getFactory().server(), tag, size);

                // 如果有结果，直接返回
                if (optIds.isPresent()) {
                    return optIds;
                }

                // 重试等待,字数递增休眠时间，避免所有请求持续在Redis服务商运行
                Thread.sleep(i * i);
            } catch (Exception e) {
                // logger.warn("使用Redis服务生成ID失败.", e);
            }
        }

        // 运行到这里表示整个ID的生成都失败了，记录日志
        logger.error("在重试 {} 次后，没有成功生成ID", maximumAttempts);
        return Optional.absent();
    }

    private Optional<List<IdObject>> generateIds(final RedisServerConf server, final String tag, final long size) {
        Optional<IdResponse> optResponse = executeLuaScript(server, tag, size);
        List<IdObject> list = new ArrayList<IdObject>();

        if (!optResponse.isPresent()) {
            return Optional.absent();
        }

        IdResponse response = optResponse.get();

        // 生成ID集合了
        List<IdObject> ids = new ArrayList<IdObject>();

        for (long i = response.startSequence; i <= response.endSequence; ++i) {
            ids.add(new IdObject(response.timeSeconds, response.timeMicroseconds, response.logicalShardId, i));
        }

        return Optional.of(ids);
    }

    /**
     * 提交Lua脚本及参数
     * @param server RedisServerConf
     * @param size 获取结果size
     * @return IdRedisResponse
     */
    @SuppressWarnings("unchecked")
    private Optional<IdResponse> executeLuaScript(final RedisServerConf server, final String tag, final long size) {
        // 要提交的参数: tag, max_sequence, size
        final String[] args = new String[]{tag, String.valueOf(IdObject.MAX_SEQUENCE), String.valueOf(size)};
        JedisPool jedisPool = server.getPool();
        Jedis jedis = jedisPool.getResource();

        try {
            Optional<IdResponse> response = evalLuaSha(jedis, server.getLuaSha(), args);

            if (response.isPresent()) {
                return response;
            }

            // 若没有能够正确取到结果
            server.loadLuaScript();

            return evalLuaSha(jedis, server.getLuaSha(), args);
        } catch (JedisConnectionException e) {
            if (jedis != null) {
                jedisPool.returnResourceObject(jedis);
                jedis = null;
            }
            // logger.error("Redis Generate Id error!", e);
            throw new IdGeneratorException("Redis Generate Id error!", e);
        } catch (IOException e) {
            // logger.error("Can't load Redis Id Generator Lua Script File with : {}", server.getLuaFilePath(), e);
            throw new IdGeneratorException("Redis Generate Id error!", e);
        } finally {
            if (jedis != null) {
                jedisPool.returnResourceObject(jedis);
            }
        }
    }

    /**
     * 执行LuaSha
     * @param jedis jedis实例
     * @param luaSha luaSha字符串调用lua脚本
     * @param args 传入脚本参数
     * @return 返回值
     */
    @SuppressWarnings("unchecked")
    private Optional<IdResponse> evalLuaSha(final Jedis jedis, final String luaSha, final String[] args) {
        return Optional.of(new IdResponse((List<Long>) jedis.evalsha(luaSha, args.length, args)));
    }

    public static class IdResponse {
        /** 返回数据结果集中 秒时间数据 位置 */
        // private static final int TIME_SECONDS_INDEX = 0;
        /** 返回数据结果集中 微秒时间数据 位置 */
        // private static final int TIME_MICROSECONDS_INDEX = 1;
        /** 返回数据结果集中 起始序号 位置 */
        // private static final int START_SEQUENCE_INDEX = 2;
        /** 返回数据结果集中 结束序号 位置 */
        // private static final int END_SEQUENCE_INDEX = 3;
        /** 返回数据结果集中 逻辑分片ID 位置 */
        // private static final int LOGICAL_SHARD_ID_INDEX = 4;

        /** 起始序列号 */
        private final long startSequence;
        /** 结束序列号 */
        private final long endSequence;
        /** 逻辑分片ID */
        private final long logicalShardId;
        /** 秒时间（unix时间戳） */
        private final long timeSeconds;
        /** 微秒时间（一秒内） */
        private final long timeMicroseconds;

        public IdResponse(final List<Long> results) {
            this.timeSeconds = results.get(0);
            this.timeMicroseconds = results.get(1);
            this.startSequence = results.get(2);
            this.endSequence = results.get(3);
            this.logicalShardId = results.get(4);
        }
    }

}
